/**
 * Canvas quan trọng nhất của game
 * User chơi game trên Canvas này
 */

import java.util.Calendar;
import java.util.Date;
import java.util.Vector;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.LayerManager;
import javax.microedition.lcdui.game.TiledLayer;

public class BoardCanvas extends GameCanvas implements Runnable {
	
	public static final String[] GAME_TYPE = { "LINES", "SQUARES", "BLOCKS" };
	private static final int BGR_COLOR = 0x000000;
	private static final int PAN_DELAY = 50;
	
	/**
	 * Nhìn vào file ball.png để hình dung
	 * Các frame (ô vuông chứa 1 trái bi) được đánh số thứ tự từ trên xuống
	 * dưới, trái qua phải.
	 * Khi user click 1 trái bi, ta có hiệu ứng tại trái bi đó, nên sẽ lần lượt
	 * thay đổi frame bằng các vị trí thứ tự cách nhau 0, -8, 0, .. như dưới
	 */
	private static final int[] BALL_SELECTED = { 0, -8, 0, -8, 0, 8, 0, 8 };
	
	/**
	 * Màu sắc ô vuông đang được chọn (chứ chưa click)
	 */
	private static final int CURSOR_COLOR = 0xFF0000;
	
	private Display display;
	
	/**
	 * Trạng thái game của board này, chỉ có 1
	 */
	public GameShape shape;
	
	/**
	 * Lưu lại trạng thái game phòng khi user click Undo
	 */
	private GameShape oldShape;
	
	private MenuCanvas menu;
	
	private int width;
	private int height;
	
	private Thread thread;
	
	private LayerManager layerManager;
	
	private TiledLayer tileBoard;
	/**
	 * Kích thước mỗi ô vuông (có 9x9=81 ô)
	 */
	private int cellSize;
	/**
	 * Kích thước = 9 x cellSize
	 */
	private int boardSize;
	
	/**
	 * Tọa độ X bắt đầu của board
	 * Do ta cần cho board nằm chính giữa màn hình
	 */
	private int boardX;
	
	/**
	 * Tọa độ Y bắt đầu của board
	 */
	private int boardY;
	
	/*
	 * Tọa độ con trỏ hiện tại (viên bi được chọn)
	 */
	private int cursorX;
	private int cursorY;
	
	/*
	 * Tọa độ viên bị được click
	 */
	private int selectedX;
	private int selectedY;
	/*
	 * Màu viên bi đang được click
	 */
	private byte selectedColor;
	private int selectedIndex;
	private int selectedFrame;
	
	private Vector path;
	private int pathPos;
	
	private Vector goodBalls;
	private int blinkState;
	
	private TileScore tileScore;
	
	private Rectangle menuRect;
	private Rectangle undoRect;
	
	private MediaPlayer media;
	private HighScoreCanvas highScore;
	
	public BoardCanvas(Display display, MenuCanvas prev, MediaPlayer media,
			HighScoreCanvas highScore) {
		super(false);
		this.media = media;
		this.highScore = highScore;
		setFullScreenMode(true);
		
		this.display = display;
		this.menu = prev;
		
		width = getWidth();
		height = getHeight();
		
		layerManager = new LayerManager();
		setupTileBoard();
		setupTileScore();
		setupRectangle();
	}
	
	private void setupTileBoard() {
		cellSize = ThemeManager.Balls.getWidth() / 8;
		boardSize = cellSize * 9;
		boardX = (width - boardSize) / 2;
		boardY = (height - boardSize) / 2 + 5;
		tileBoard = new TiledLayer(9, 9, ThemeManager.Balls, cellSize, cellSize);
		tileBoard.setPosition(boardX, boardY);
		tileBoard.fillCells(0, 0, 9, 9, 1);
		layerManager.append(tileBoard);
	}
	
	private void setupTileScore() {
		tileScore = new TileScore(4);
		tileScore.setPosition(
			boardX + boardSize - tileScore.getWidth(),
			boardY - 5 - tileScore.getHeight()
		);
		layerManager.append(tileScore);
	}
	
	private void setupRectangle() {
		menuRect = new Rectangle(
				boardX + 4,
				boardY + boardSize,
				4 * ThemeManager.Small.maxWidth,
				ThemeManager.Small.pieceHeight);
		int temp = 4 * ThemeManager.Small.maxWidth;
		undoRect = new Rectangle(
				boardX + boardSize - temp,
				boardY + boardSize,
				temp,
				ThemeManager.Small.pieceHeight);
	}
	
	public void setGameShape(GameShape gs) {
		shape = gs;
		refreshAllCells();
		cursorX = 4;
		cursorY = 4;
		selectedX = -1; // No ball selected
		
		path = null; // No move any ball.
		oldShape = null; // Nothing to undo, because undo only 1 level.
	}
	
	private void refreshAllCells() {
		for (int i = 0; i < 9; i++)
			for (int j = 0; j < 9; j++)
				refreshCell(i, j);
	}
	
	private void refreshCell(int i, int j, byte color) {
		tileBoard.setCell(i, j, getTileIndex(color));
	}
	
	private void refreshCell(int i, int j) {
		refreshCell(i, j, shape.a[i][j]);
	}
	
	private int getTileIndex(byte color) {
		if (color < 0 && !Option.isShowNext) return 1;
		return color >= 0 ? 9 + color : 25 - color;
	}
	
	private void updateCursor(Graphics g) {
		g.setColor(CURSOR_COLOR);
		g.drawRect(boardX + cellSize * cursorX, boardY + cellSize * cursorY,
				cellSize, cellSize);
	}
	
	private void updateSelected() {
		if (selectedX == -1) return;
		selectedFrame += BALL_SELECTED[selectedIndex++];
		tileBoard.setCell(selectedX, selectedY, selectedFrame);
		if (selectedIndex == BALL_SELECTED.length) selectedIndex = 0;
	}
	
	private void updateMoveBall() {
		if (path == null) return;
		Point p1 = (Point) path.elementAt(pathPos);
		if (pathPos == path.size() - 1) {
			if (Option.isSound) {
				media.playPut();
			}
			checkGoodBalls(p1);
			path = null; // Stop move, no move any ball
			return;
		}
		Point p2 = (Point) path.elementAt(++pathPos);
		refreshCell(p1.x, p1.y);
		refreshCell(p2.x, p2.y, selectedColor);
	}
	
	private Vector delegateCheck(Point p1) {
		switch (shape.type) {
		case 0:
			return Algorithms.checkLines(shape.a, p1.x, p1.y);
		case 1:
			return Algorithms.checkSquares(shape.a, p1.x, p1.y);
		default:
			return Algorithms.checkBlocks(shape.a, p1.x, p1.y);
		}
	}
	
	private void checkGoodBalls(Point p1) {
		goodBalls = delegateCheck(p1);
		if (goodBalls == null) {
			/*
			 *  Appear the next balls
			 */
			Vector nextBalls = new Vector(3);
			for (int i = 0; i < 9; i++)
				for (int j = 0; j < 9; j++)
					if (shape.a[i][j] < 0) {
						nextBalls.addElement(new Point(i, j));
						shape.a[i][j] = (byte) -shape.a[i][j];
						refreshCell(i, j);
					}
			/*
			 * After the next balls are showed, check if any good lines
			 */
			Vector totalGood = new Vector();
			for (int i = nextBalls.size() - 1; i >= 0; i--) {
				Point p = (Point) nextBalls.elementAt(i);
				Vector vt = delegateCheck(p);
				if (vt != null) {
					totalGood = Algorithms.merge(totalGood, vt);
				}
			}
			
			if (totalGood.size() > 0) {
				goodBalls = totalGood;
				blinkState = 0;
				incScore(totalGood.size());
			} else if (Algorithms.countEmpty(shape.a) < 3) {
				/*
				 *  Game over
				 */
				highScore.isBackToMenu = false;
				Calendar c = Calendar.getInstance();
				c.setTime(new Date());
				highScore.insertScore(
						new ScoreRecord(MyIO.convertToStr(c), shape.score));
				display.setCurrent(highScore);
				return;
			}
			Algorithms.addNextColor(shape.a);
			for (int i = 0; i < 9; i++)
				for (int j = 0; j < 9; j++)
					if (shape.a[i][j] < 0) {
						refreshCell(i, j);
					}
		} else {
			blinkState = 0;
			incScore(goodBalls.size());
		}
	}
	
	private void incScore(int size) {
		int min = 5;
		if (shape.type == 1) min = 4;
		else if (shape.type == 2) min = 7;
		shape.score += Algorithms.calcuScore(size, min);
		if (Option.isSound) {
			media.playScore();
		}
	}
	
	private void updateScore() {
		tileScore.setScore(shape.score);
	}

	private void updateDestroy() {
		if (goodBalls == null) return;
		blinkState++;
		boolean b = blinkState % 2 == 0;
		for (int i = goodBalls.size() - 1; i >= 0; i--) {
			Point p = (Point) goodBalls.elementAt(i);
			refreshCell(p.x, p.y, b ? 0 : shape.a[p.x][p.y]);
		}
		if (blinkState == 8) {
			for (int i = goodBalls.size() - 1; i >= 0; i--) {
				Point p = (Point) goodBalls.elementAt(i);
				shape.a[p.x][p.y] = 0;
			}
			goodBalls = null;
		}
	}

	protected void showNotify() {
		thread = new Thread(this);
		thread.start();
	}
	
	protected void hideNotify() {
		thread = null;
	}

	public void run() {
		Graphics g = getGraphics();
		Thread t = Thread.currentThread();
		try {
			while (t == thread) {
				updateSelected();
				updateMoveBall();
				updateDestroy();
				updateScore();
				render(g);
				Thread.sleep(PAN_DELAY);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	private void render(Graphics g) {
		g.setColor(BGR_COLOR);
		g.fillRect(0, 0, width, height);
		layerManager.paint(g, 0, 0);
		updateCursor(g);
		/*
		 * Draw "SCORE" text
		 */
		ThemeManager.drawString(g, "SCORE",
				tileScore.getX() - 5 * ThemeManager.Small.maxWidth - 8,
				tileScore.getY(), ThemeManager.SMALL_STYLE, Graphics.TOP);
		/*
		 * Draw type of game text
		 */
		ThemeManager.drawString(g, GAME_TYPE[shape.type],
				boardX + 4, boardY - 4, ThemeManager.BIG_STYLE, Graphics.BOTTOM);
		/*
		 * Draw "MENU" text
		 */
		ThemeManager.drawString(g, "MENU", menuRect.x, menuRect.y,
				ThemeManager.SMALL_STYLE, Graphics.TOP);
		/*
		 * Draw "UNDO" text
		 */
		ThemeManager.drawString(g, "UNDO", undoRect.x, undoRect.y,
				ThemeManager.SMALL_STYLE, Graphics.TOP);
		
		flushGraphics();
	}

	private void backToMenu() {
		menu.showRootMenu();
		menu.highlight = 0;
		display.setCurrent(menu);
	}
	
	protected void pointerPressed(int x, int y) {
		if (menuRect.inside(x, y)) {
			backToMenu();
			return;
		}
		if (undoRect.inside(x, y)) {
			undo();
			return;
		}
		if (x > boardX && x < boardX + boardSize &&
				y > boardY && y < boardY + boardSize) {
			cursorX = (x - boardX) / cellSize;
			cursorY = (y - boardY) / cellSize;
			fireAction();
		}
	}
	
	protected void keyPressed(int keyCode) {
		if (keyCode == -6) {
			backToMenu();
			return;
		}
		if (keyCode == -7) {
			undo();
			return;
		}
		int action = getGameAction(keyCode);
		switch (action) {
		case Canvas.UP:
			cursorY--;
			if (cursorY < 0) cursorY = 8;
			break;
		case Canvas.DOWN:
			cursorY++;
			if (cursorY > 8) cursorY = 0;
			break;
		case Canvas.LEFT:
			cursorX--;
			if (cursorX < 0) cursorX = 8;
			break;
		case Canvas.RIGHT:
			cursorX++;
			if (cursorX > 8) cursorX = 0;
			break;
		case Canvas.FIRE:
			fireAction();
		}
	}

	private void fireAction() {
		byte color = shape.a[cursorX][cursorY];
		
		if (color > 0) {
			if (selectedX != -1) {
				refreshCell(selectedX, selectedY);
			}
			selectedX = cursorX;
			selectedY = cursorY;
			selectedFrame = getTileIndex(color);
			selectedIndex = BALL_SELECTED.length - 1;
		} else if (selectedX > -1) {
			path = Algorithms.havePath(shape.a, selectedX, selectedY,
					cursorX, cursorY);
			if (path != null) {
				oldShape = shape.makeCopy();
				selectedColor = shape.a[selectedX][selectedY];
				shape.a[cursorX][cursorY] = shape.a[selectedX][selectedY];
				shape.a[selectedX][selectedY] = 0;
				refreshCell(selectedX, selectedY, selectedColor);
				selectedX = -1;
				pathPos = 0;
			}
		}
	}

	private void undo() {
		if (oldShape == null) return;
		shape.a = oldShape.a;
		shape.type = oldShape.type;
		shape.score = oldShape.score;
		
		refreshAllCells();
		selectedX = -1; // No ball selected
		oldShape = null; // Nothing to undo, because undo only 1 level.
	}

}
